from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.api import users
from google.appengine.api import urlfetch

import utils
import logging
import urllib
import settings
import cgi
import random
import yaml
import datetime
import json

import os, os.path, logging
log = logging.getLogger(os.path.basename(__file__))


SIZE_CLASSES=[
    '10x10',
    '20x20',
    '50x50',
    '100x100',
    '200x200',
    '400x400',
    ]

class FridgeImage(db.Model):
    name        = db.StringProperty()
    desc        = db.StringProperty()
    owner       = db.UserProperty()
    ips         = db.StringProperty()
    ext         = db.StringProperty()
    
    content     = db.BlobProperty()
    mime_type   = db.StringProperty()
    
    published   = db.BooleanProperty()
    secret      = db.StringProperty()
    added       = db.DateTimeProperty(auto_now_add=True)
    modified    = db.DateTimeProperty(auto_now=True)
    
    parent_image= db.StringProperty()
    size        = db.IntegerProperty()
    
    width       = db.IntegerProperty()
    height      = db.IntegerProperty()




mimetypes = {
    'jpeg': 'image/jpeg',
    'jpg':  'image/jpeg',
    'tiff':  'image/tiff',
    'tif':   'image/tiff',
    'gif':   'image/gif',
    'png':   'image/png',
    }



class FridgeMain(webapp.RequestHandler):
    def get(self):
        self.response.out.write(utils.render_to_mako('fridge/fridge.html', {
                'size_classes':SIZE_CLASSES, 
                'servicesurl':settings.SERVICESURL,
                'DOMAIN':settings.DOMAIN,
                'TOPDOMAIN':settings.TOPDOMAIN,
                'username': 'user_' + utils.random_string(length=6),
                }) )
        return
    
    def post(self):
        if 'file' not in self.request.POST:
            self.error(400)
            self.response.out.write("file not specified!")
            return
        
        if self.request.POST.get('file', None) is None:
            self.error(400)
            self.response.out.write("no file not specified!")
            return
        
        if isinstance(self.request.POST.get('file'), unicode):
            file_name = 'unknown.jpg'
            file_data = self.request.POST['file']
        else:
            if not self.request.POST.get('file', None).filename:
                self.error(400)
                self.response.out.write("no filename!")
                return
            file_data = self.request.POST['file'].file.read()
            file_name = self.request.POST['file'].filename
        
        if len(file_data) > 1024*1024:
            self.error(400)
            self.response.out.write("size of the image is more than 1mb!")
            return
        
        
        im              = FridgeImage()
        im.content      = file_data
        im.name         = file_name
        im.desc         = cgi.escape( self.request.POST.get('desc', '')[:80] ).replace('"', '&quot;').replace("'", '&rsquo;')
        
        n = im.name.lower()
        im.mime_type, im.height, im.width = utils.getImageInfo(im.content)
        ext = 'jpg'
        for e in ['jpeg', 'jpg', 'png', 'tif', 'tiff', 'gif']:
            if n.endswith(e):
                ext = e
                break
        im.ext          = ext
        
        im.secret       = utils.random_string()
        im.published    = True
        im.size         = None
        im.owner        = users.get_current_user()
        im.ips          = '%r %r %r' % (self.request.environ.get('REMOTE_HOST', ''), 
                                        self.request.headers.get('HTTP_X_FORWARDED_FOR',''), 
                                        self.request.headers.get('X_FORWARDED_FOR','') )
        im.published    = True
        im.save()
        
        ratio = float(im.width) / float(im.height)
        
        mag         = FridgeMagnet(image_key=str(im.key()),image_id=im.key().id())
        mag.desc    = im.desc
        mag.width   = 100
        mag.height  = int(float(mag.width) * ratio)
        mag.x       = 0
        mag.y       = 0
        mag.save()
        mag.signal_create()
        
        
        im_s = FridgeImage.all().filter("published =", True).filter("size =", None).order("-modified").fetch(50)
        if len(im_s) > 38:
            for im in im_s[38:]:
                mag = FridgeMagnet.all().filter('image_key =', str(im.key() )).get()
                if mag:
                    mag.signal_delete()
                    mag.delete()
                for i in FridgeImage.all().filter("parent_image =", str(im.key())).fetch(20):
                    i.delete()
                im.delete()
        
        
        self.response.out.write('Your image will be seen in a while. Thanks!')
        #self.redirect('./')
        return


class FridgeUploadImage(webapp.RequestHandler):
    def post(self, key):
        im = db.get(db.Key(key))
        if not im:
            self.error(404)
            return
        
        if self.request.POST.get('id', '').encode() != im.secret:
            self.error(400)
            return
        
        im.content  = self.request.POST['file'].file.read()
        im.mime_type, im.height, im.width = utils.getImageInfo(im.content)
        
        im.published= True
        im.save()
        
        logging.info("%r->%r(%r) updated" % (im.parent_image, im.key(), SIZE_CLASSES[im.size] ) )
        self.response.out.write("ok")
        return

size_cache = {}
class FridgeGetImage(webapp.RequestHandler):
    def get(self, key, u_size):
        # im_parent = db.get(db.Key(key))
        size = u_size.encode()
        key  = key.encode()
        if size == '0x0':
            im_c = db.get(db.Key(key))
            if not im_c:
                self.error(404)
                return
            self.response.headers["Content-Type"] = im_c.mime_type.encode()
            self.response.headers.add_header("Expires", "Thu, 01 Dec 2014 16:00:00 GMT")
            self.response.out.write(im_c.content)
            return
            
        if size not in SIZE_CLASSES:
            w1, h1 = map(int,size.split('x'))
            for s in SIZE_CLASSES:
                w2, h2 = map(int,s.split('x'))
                if w2 > w1 or h2 > w2:
                    size = s
                    break
            else:
                size = s
            log.warning('req %r, resp %r' % (u_size,size) )
            self.redirect('./%s_%s' % (key, size) )
            return
        
        im_c = FridgeImage.all().filter('parent_image =', key ).filter('size =', SIZE_CLASSES.index(size) ).get()
        if not im_c:
            im_parent = db.get(db.Key(key))
            if not im_parent:
                self.error(404)
                return
                
            im          = FridgeImage()
            im.name     = im_parent.name
            im.desc     = im_parent.desc
            im.owner    = im_parent.owner
            im.ips      = im_parent.ips
            im.mime_type= im_parent.mime_type
            im.secret   = im_parent.secret
            im.ext      = im_parent.ext
            
            im.parent_image = key
            im.size         = SIZE_CLASSES.index(size)
            im.published    = False
            # theoretically there can be two saves in the same time, that's okay ;P
            im.save()
            
            q = urllib.urlencode({
                'id':       im.secret,
                'size':     size,
                'action':   'resize', #if im.ext != 'gif' else 'avatar',
                'quality':  '80',
                'bgcolor':  '#EEEEEE' if im.ext != 'gif' else 'transparent',
                'file_name':im.name,
                'callback': 'http://%s/fridge/upload/%s' % ( settings.DOMAIN, im.key() ),
            })
            try:
                resp = urlfetch.fetch('http://resizer.%s/resizer/resize?%s' % (settings.SERVICESURL,q), payload=im_parent.content, method=urlfetch.POST, headers={}, allow_truncated=True)
            except urlfetch.DownloadError:
                logging.error("image %r->%r failed to schedule resize! fix it!" % (key, im.key()) )
                im.delete()
                self.error(500)
                return
            
            if resp.status_code != 200:
                logging.error("image %r->%r failed to schedule resize! fix it (%.60r)!" % (key, im.key(), resp.content) )
                im.delete()
                self.error(500)
                return
            
            im_c = im
        
        if not im_c.published:
            im_s = FridgeImage.all().filter('parent_image =', key ).filter('published =', True).filter('size >', SIZE_CLASSES.index(size)).order('size').get()
            if not im_s:
                self.redirect('./%s_%s' % (key, '0x0' ) )
                return
            im_c = im_s
        
        self.response.headers.add_header("Expires", "Thu, 01 Dec 2014 16:00:00 GMT")
        self.response.headers["Content-Type"] = im_c.mime_type.encode()
        self.response.out.write(im_c.content)
        return



class FridgeMagnetSingleton(db.Model):
    secret        = db.StringProperty()
    channel_id    = db.StringProperty()
    event_key     = db.StringProperty()
    destroy_key   = db.StringProperty()
    last_event_id = db.IntegerProperty()
    events        = db.ListProperty(unicode)
    logged_users  = db.ListProperty(unicode)

    @staticmethod
    def get_singleton():
        s = FridgeMagnetSingleton.all().fetch(1)
        if len(s) < 1:
            s           = FridgeMagnetSingleton()
            s.secret    = utils.random_string()
            s.channel_id = 'notset'
            s.last_event_id = 0
            s.save()
            s = FridgeMagnetSingleton.all().fetch(1)
        s = s[0]
        assert s
        return s



class FridgeMagnet(db.Model):
    image_key   = db.StringProperty()
    image_id    = db.IntegerProperty()
    desc        = db.StringProperty()
    x           = db.IntegerProperty()
    y           = db.IntegerProperty()
    width       = db.IntegerProperty()
    height      = db.IntegerProperty()
    
    def get_create_msg(self):
        dd = {
            'desc':     self.desc.encode(),
            'key':      self.image_key.encode(),
            'x':        self.x,
            'y':        self.y,
            'width':    self.width,
            'height':   self.height,
        }
        return 'admin %i N %s' % (self.image_id, json.write(dd) )
    
    def signal_create(self):
        cha = FridgeMagnetSingleton.get_singleton()
        event_id = send_event(cha.channel_id, self.get_create_msg(), cha.event_key)
        
        def mod(key):
            obj = db.get(key)
            if obj.last_event_id < event_id:
                obj.last_event_id = event_id
            else:
                raise db.Rollback()
            obj.put()
        db.run_in_transaction(mod, cha.key())
        
    def signal_delete(self):
        cha = FridgeMagnetSingleton.get_singleton()
        msg = 'admin %i D' % (self.image_id)
        event_id = send_event(cha.channel_id, msg, cha.event_key)
        
        def mod(key):
            obj = db.get(key)
            if obj.last_event_id < event_id:
                obj.last_event_id = event_id
            else:
                raise db.Rollback()
            obj.put()
        db.run_in_transaction(mod, cha.key())


class FridgeMagnetChannelUri(webapp.RequestHandler):
    def get(self):
        cha = FridgeMagnetSingleton.get_singleton()
        
        # test if channel exists
        try:
            resp = urlfetch.fetch('%s/%s/?rand=%f' % (settings.COMETURL, cha.channel_id, random.random() ), method=urlfetch.GET, headers={}, allow_truncated=True)
        except urlfetch.DownloadError:
            resp = None
        
        # channel doesn't exist - create new one
        if resp and resp.status_code != 200:
            uri = 'http://%s/fridge/user_action?secret=%s' % (settings.DOMAIN, cha.secret)
            q = urllib.urlencode({
                'public':      'True',
                'event_callback_uri':uri,
                'login_callback_uri':uri,
                'logout_callback_uri':uri,
                'disconnect_time': 8,
            })
            
            # this must go good - no try/except
            resp = urlfetch.fetch('http://comet.%s/comet/create_channel' % (settings.SERVICESURL), payload=q, method=urlfetch.POST, headers={}, allow_truncated=False)
            if resp.status_code != 200:
                logging.error("Failed to create channel. query=%r status=%i content=%.250r!" % (q, resp.status_code, resp.content) )
                self.error(500)
                return
        
            dd = yaml.load(resp.content)
            
            def mod(key):
                obj = db.get(key)
                # noone modified the record?
                if cha.channel_id == obj.channel_id:
                    obj.channel_id      = dd['channel_id']
                    obj.event_key       = dd['event_key']
                    obj.destroy_key     = dd['destroy_key']
                    obj.logged_users    = []
                    obj.events          = []
                    obj.last_event_id   = 0
                    obj.added_time      = datetime.datetime.utcnow()
                else:
                    raise db.Rollback()
                obj.put()
            db.run_in_transaction(mod, cha.key())
            
            cha = db.get(cha.key())
        
            
        o = {
            'channel_id':       cha.channel_id,
            'events':           [ mag.get_create_msg().encode('utf-8') for mag in FridgeMagnet.all().fetch(50) ],
            'logged_users':     cha.logged_users,
            'last_event_id':    cha.last_event_id,
        }
        self.response.out.write(json.write(o))
        return

class FridgeMagnetUserAction(webapp.RequestHandler):
    def get(self):
        cha = FridgeMagnetSingleton.get_singleton()

        action   = self.request.GET.get('action', None)
        secret   = self.request.GET.get('secret', None)
        channel_id = self.request.GET.get('channel_id', None)
        if not action or cha.channel_id != channel_id:
            log.error('bad action/channelid/secret %r %r %r' % (action, channel_id, secret))
            self.error(500)
            return
        
        event_id   = int(self.request.GET.get('event_id', 0) )
        data       = self.request.GET.get('data', None)
        
        if action == 'event':
            username, image_id, t, a, b = data.split(' ')
            if t == 'M':
                x, y, width, height = int(a), int(b), None, None
                log.warning(' x %r y %r' % (a,b))
            elif t == 'S':
                x, y, width, height = None, None, int(a), int(b)
            else:
                log.error('bad tyle %r' % t)
                self.error(500)
                return
            
            mag = FridgeMagnet.all().filter('image_id =', int(image_id) ).get()
            if not mag:
                return
            
            log.warning('have   %r by %r %r  scale %r %r ' % (mag.image_id, mag.x, mag.y,mag.width,mag.height))
            log.warning('moving %r by %r %r  scale %r %r ' % (mag.image_id, x,y,width,height))
            def mod(key):
                obj = db.get(key)
                if x or y:
                    obj.x = obj.x + x
                    obj.y = obj.y + y
                elif width or height:
                    obj.width  = width
                    obj.height = height
                obj.put()
            db.run_in_transaction(mod, mag.key())
            mag = db.get(mag.key() )
            log.warning('get    %r by %r %r  scale %r %r ' % (mag.image_id, mag.x, mag.y,mag.width,mag.height))
        elif action == 'login':
            assert secret == cha.secret
            pass
        elif action == 'logout':
            assert secret == cha.secret
            pass
        
        def mod(key):
            obj = db.get(key)
            if obj.last_event_id < event_id:
                obj.last_event_id = event_id
            else:
                raise db.Rollback()
            obj.put()
        
        db.run_in_transaction(mod, cha.key())
        return
        

def send_event(channel_id, msg, event_key):
    q = urllib.urlencode({
        'data':      msg,
        'event_key': event_key,
    })
    resp = urlfetch.fetch('http://comet.%s/comet/%s/event' % (settings.SERVICESURL, channel_id), payload=q, method=urlfetch.POST, headers={}, allow_truncated=False)
    if resp.status_code != 200:
        logging.error("sending event failed to ch %s" % channel_id)
    
    o = yaml.load(resp.content)
    return o['event_id']
